iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 29
3
AI & Data

深入淺出搜尋引擎和自然語言處理系列 第 29

Day 29: 電腦怎麼知道「說」跟「曰」意義相近?關於文字相似度

  • 分享至 

  • xImage
  •  

總覽

今天我們要使用昨天說到的Lin similarity來計算字與字之間的相似度。我們將會使用Brown Corpus作為訓練文集,以及Wordnet中的文字關係架構來計算文字相似度。今天的任務會分成三部分:

  1. 預處理
  2. 計算相似度 - Lin similarity
  3. 將計算的相似度與黃金標籤(人工標籤)進行比對

1. 預處理

今天我們會利用一個常見的Word Similarity Dataset:Similarity-353。我已經把dataset存起來放到Github上,大家可以在這裡下載,記得把dataset和這個notebook放在同一個資料夾裡面。

這個Dataset長這樣:

Word 1    Word 2    Human (mean)    1    2    3    4    5    6    7    8    9    10    11    12    13    
love    sex    6.77    9    6    8    8    7    8    8    4    7    2    6    7    8    
tiger    cat    7.35    9    7    8    7    8    9    8.5    5    6    9    7    5    7    
tiger    tiger    10.00    10    10    10    10    10    10    10    10    10    10    10    10    10    
book    paper    7.46    8    8    7    7    8    9    7    6    7    8    9    4    9    
computer    keyboard    7.62    8    7    9    9    8    8    7    7    6    8    10    3    9

除了第一列是header之外,其他每一列都是資料。資料中第一行是文字1、第二行是文字2、第三行是黃金標籤(人類審查),之後的每一行是由不同的審查員審查後給的評分。

這裡我們資料存進Python dictionary中,以文字1和文字2的tuple為key,以黃金標籤為value,長得就像:{("tiger", "cat"): 7.35, ...}。 這個dataset中存著許多稀有字,所以我們需要把他們去除掉,讓我們的dataset跟訓練資料更能夠配合。所以第一步驟我們要做的就是去除稀有字,來產生一個小一些的測試集供我們未來測試文字相似度。

首先,我們基於文字在Brown Corpus中的document frequency來判斷該字是不是稀有字。我們把Brown Corpus中的每個段落視為一個檔案,利用NLTK Brown內建的 paras 方法來讀取,同時去掉那些非英文字母的字,接著進行lower case和lemmatization。若是文字1和文字2中其中一個字的document frequency小於8,那麼我們就將之視為稀有字,並且把這個文字組合移除。

import nltk
from nltk.corpus import brown
from nltk.corpus import wordnet
#nltk.download('brown')
#nltk.download('wordnet')

# filtered_gold_standard 儲存移除稀有自後的文字組合與黃金標籤
filtered_gold_standard = {}

from collections import Counter
doc_freqs = Counter()

# lemmatize
lemmatizer = nltk.stem.wordnet.WordNetLemmatizer()
def lemmatize(word):
    lemma = lemmatizer.lemmatize(word,'v')
    if lemma == word:
        lemma = lemmatizer.lemmatize(word,'n')
    return lemma

# 移除有稀有字的文字組合
def remove_word_pair(dictionary, li):
    for key in list(dictionary.keys()):
        if key[0] not in li or key[1] not in li:
            dictionary.pop(key, None)

# 讀取資料,同時將文字組合與相似度存進filtered_gold_standard
# 也將每一個獨特自行存到doc_freqs Counter
with open("set1.tab") as f:
    next(f)
    for line in f:
        line_list = line.split("\t")
        filtered_gold_standard[(line_list[0], line_list[1])] = float('%.2f' % float(line_list[2]))
        for i in range(2):
            doc_freqs[line_list[i]] = 0

bwn_par = brown.paras() # 未處理的Brown Corpus段落
norm_par = [] # 預處理過的Brown Corpus段落

# 進行預處理
for i in range(len(bwn_par)):
    para_sentence = []
    for j in range(len(bwn_par[i])):
        para_sentence.append([lemmatize(word.lower()) for word in bwn_par[i][j] if word.isalpha()])
    norm_par.append(sum(para_sentence,[]))

# 計算每個處理過的字的document frequency
for word in doc_freqs:
    for i in range(len(norm_par)):
        if word in norm_par[i]:
            doc_freqs[word] += 1
            
# 將document frequency < 8 的稀有字去除
for key in list(doc_freqs.keys()):
    if doc_freqs[key] < 8:
        doc_freqs.pop(key,None)

remove_word_pair(filtered_gold_standard, doc_freqs)

print(len(filtered_gold_standard))
print(filtered_gold_standard)

第二段預處理,我們要將有太多的歧異的字去除。這裡我們會根據NLTK中WordNet所提供的資料,將那些沒有單一主要字義的字去掉。我們定義單一主要字義為:1) 只有一個意思(也就是只有一個synset),或2) 最常見的意思是第二常見的意思之count在四倍以上(WordNet有提供 count() 方法)。除了去除沒有單一主要字義的字,我們也要去掉那些單一主要字義不是名詞的字(在Synset的資料中也會顯示)。只要文字組合中有一個字是需要去除的,整個文字組合都需要被移除。

我們會將經過第二段預處理的文字組合與相似度存進 final_gold_standard

# final_gold_standard 儲存最終版本的文字組合與相似度
final_gold_standard = {}

import operator

# primary_sense 儲存文字與主要字義的組合
primary_sense = {}

# word_types 儲存所有字型
word_types = []

# 將所有自行加到 word_types 中
final_gold_standard = filtered_gold_standard.copy()
for key in list(filtered_gold_standard.keys()):
    for i in range(2):
        if key[i] not in word_types:
            word_types.append(key[i])

# 檢查這個字是否符合單一主要字義標準,若是,則留下該字
def should_keep(word):
    lemma_count = {} # k: (i, j, .pos()), v: .count()
    
    for i in range(len(wordnet.synsets(word))):
        for j in range(len(wordnet.synsets(word)[i].lemmas())):
            if wordnet.synsets(word)[i].lemmas()[j].name().lower() == word:
                lemma_count[(i,j,wordnet.synsets(word)[i].pos())] = wordnet.synsets(word)[i].lemmas()[j].count()
            else:
                lemma_count[(i,j,wordnet.synsets(word)[i].pos())] = 0
    
    # 照著count數排序
    lemma_count = sorted(lemma_count.items(), key=operator.itemgetter(1), reverse=True)
    
    # 將是名詞且有單一主要字義的字留下
    if len(lemma_count) > 0:
        if len(wordnet.synsets(word)) == 1 and lemma_count[0][0][2] == 'n':
            primary_sense[word] = lemma_count[0][0][0]
            return True
        elif len(lemma_count) > 1 and lemma_count[0][1] >= (lemma_count[1][1] * 4) and lemma_count[0][0][2] == 'n':
            primary_sense[word] = lemma_count[0][0][0]
            return True
    return False

word_types = [word for word in word_types if should_keep(word)]

# 將沒有達到條件的文字組合移除
remove_word_pair(final_gold_standard, word_types)

print(len(final_gold_standard))
print(final_gold_standard)

2. 使用Lin Similarity計算相似度

前面已經有黃金標籤(人工審查)的相似度了。現在,我們要使用Lin similarity來計算。我們用Brown corpus中的information content (IC)來計算。

from nltk.corpus import wordnet_ic
#nltk.download('wordnet_ic')

# lin_similarities 儲存文字組合與lin similarity
lin_similarities = {}

brown_ic = wordnet_ic.ic('ic-brown.dat')
for key in list(final_gold_standard.keys()):
    word_x, word_y = wordnet.synsets(key[0]), wordnet.synsets(key[1])
    sense_x, sense_y = primary_sense[key[0]], primary_sense[key[1]]
    lin_similarities[key] = word_x[sense_x].lin_similarity(word_y[sense_y], brown_ic)

print(lin_similarities)

3. 比對Lin similarity和黃金標籤

最後,我們使用Pearson correlation co-efficient來比對Lin similarity和黃金標籤的關聯度。利用Scipy (scipy.stats) 裡面的 pearsonr 方法來計算。Pearson correlation co-efficient的結果會落於-1到1之間,若是完美正相關則為1。

from scipy.stats import pearsonr

def cal_pearson_cor(dict2):
    dict1 = final_gold_standard
    array1 = list(dict1.values())
    array2 = list(dict2.values())
    return pearsonr(array1, array2)

print(cal_pearson_cor(lin_similarities)[0])

今天的Jupyter Notebook在這裡


上一篇
Day 28: 文字相似度- 語言學
下一篇
Day 30: 總結、心得與展望
系列文
深入淺出搜尋引擎和自然語言處理30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言